﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;

namespace SunrayEngine.Threading
{
    internal sealed class MultiThreadInvoker
        : IDisposable
    {
        #region fields
        private Thread[] threads; //The thread that will do the work.
        private Queue<Action> queue; //The queue.
        private readonly object locker = new object(); //Used to control locks.
        #endregion

        #region properties
        /// <summary>
        /// All threads in the pool.
        /// </summary>
        public Thread[] Threads { get { return threads; } }

        /// <summary>
        /// Get The currently working threads.
        /// </summary>
        public int ActiveThreads { get { return (from c in threads where c.ThreadState == ThreadState.Running select c).Count(); } }
        #endregion

        #region init
        public MultiThreadInvoker(int threads)
        {
            queue = new Queue<Action>();

            //Initialize the threads.
            this.threads = new Thread[threads];
            for(int i = 0; i < threads; i++)
                (this.threads[i] = new Thread(consume)).Start();
        }
        #endregion

        #region methods
        /// <summary>
        /// Queues a new action to be executed.
        /// </summary>
        /// <param name="action"></param>
        public void Queue(Action action)
        {
            lock (locker)
            {
                //Queue an item.
                queue.Enqueue(action);

                //Tell the threads that the queue has been updated.
                Monitor.PulseAll(locker);
            }
        }

        /// <summary>
        /// The method that runs in every thread and handels the work destrubition.
        /// </summary>
        private void consume()
        {
            Action a;
            while (true)
            {
                lock (locker)
                {
                    //Wait while the queue is empty.
                    while (queue.Count == 0)
                        Monitor.Wait(locker);
                    a = queue.Dequeue();

                    //determine if the thread should quit.
                    if (a == null)
                        return;

                    a(); //Call the delegate.
                }
            }
        }

        public void Dispose()
        {
            // Enqueue one null task per thread to shut them down.
            foreach (Thread worker in threads) Queue(null);
            foreach (Thread worker in threads) worker.Join();
        }
        #endregion
    }
}
